#ifndef __BUOLA_GUI_CWINDOW_H__
#define __BUOLA_GUI_CWINDOW_H__

#include <buola/gui.h>

#include <buola/utility/cregion.h>
#include <buola/mat/c2dtransform.h>
#include <buola/gui/cpointer.h>
#include <buola/image/ccolor.h>
#include <buola/gui/events.h>
#include <buola/datetime/clocks.h>
#include <buola/gui/cpointer.h>
#include <buola/utility/utrackable.h>

namespace buola { namespace gui {
    
///\page focus Focus model
///
/// this page should describe the focus model used in buola

///\page coordinates Coordinate systems for windows
///
/// There are four different coordinate systems:
///
/// - root coordinate system
///
/// has the origin at the upper-left corner of the screen, one unit=one pixel
///
/// - physical coordinate system
///
/// has the origin at the upper-left corner of the physical window, one unit=one pixel
///
/// - window coordinate system
///
/// has the origin at the upper-left corner of the window, including attachments


///\ingroup gui widgets
///@{

////////////////////////////////////////////////////////////////////////////

/// window base class

///
///
///
////////////////////////////////////////////////////////////////////////////

class CWindow : public UShared,public UTrackable
{
public:
    struct SHooks
    {
        std::vector<std::function<void(const CRegion&)>> mCommit;
    };
    
    //! circulation modes
    enum class ECirculate
    {
        FIRST,        //!< circulate focus to first child
        LAST,         //!< circulate focus to last child
        PREVIOUS,     //!< circulate focus to previous child
        NEXT          //!< circulate focus to next child
    };

    ///focus mode
    enum class EFocusMode : uint8_t
    {
        NONE,
        TAB=(1<<0),           //!< window is part of tab chain
        MOUSE=(1<<1),         //!< set focus when clicking
    };
    DECLARE_FRIEND_ENUM_CLASS_BITOPS(EFocusMode);

    ///paint mode
    enum class EPaintMode : uint8_t
    {
        NORMAL=0,
        INVISIBLE=(1<<0),     //!< the window is invisible (input only)
        TRANSPARENT=(1<<1),   //!< the window has transparent or translucent parts
        NOBUFFER=(1<<2),      //!< don't reserve a special buffer for this window, draw directly on parent
        OPENGL=(1<<3),        //!< allow using OpenGL commands for painting the window
    };
    DECLARE_FRIEND_ENUM_CLASS_BITOPS(EPaintMode);

    ///input mode
    enum class EInputMode : uint8_t
    {
        NORMAL=0,
        MOUSETRANSPARENT=(1<<0),
    };
    DECLARE_FRIEND_ENUM_CLASS_BITOPS(EInputMode);

    //! window attributes
    enum class EAttribs : uint8_t
    {
        NONE=0,
        MAPPABLE=(1<<0)
    };

    DECLARE_FRIEND_ENUM_CLASS_BITOPS(EAttribs)
    
private:
    //! window flags
    enum class EFlags : uint16_t
    {
        NONE=0,
        FIRSTSHOW=              (1<<1),
        MAPPED=                 (1<<2),///< the window is mapped. it now actually means it is visible
        MAPONLAYOUT=            (1<<3),///< Map() has been called. If it is not mapped, it will be in next layout.
        MODAL=                  (1<<4),
        MANAGED=                (1<<5),
        FROZEN=                 (1<<6),
        MAINWINDOW=             (1<<7),
        ATTACHED=               (1<<8),
        DOINGLAYOUT=            (1<<9) ///< the layout for this window is being calculated right now
    };
    DECLARE_FRIEND_ENUM_CLASS_BITOPS(EFlags)

    ///validity flags
    enum class EValid : uint8_t
    {
        NONE=0,
        EXTERNAL_LAYOUT=(1<<0),
        INTERNAL_LAYOUT=(1<<1),
        REGION_WHOLE=(1<<2),
        REGION_CLIENT=(1<<3),
        REGIONS=REGION_WHOLE|REGION_CLIENT
    };
    
    DECLARE_FRIEND_ENUM_CLASS_BITOPS(EValid);

public:
//! \name constructor and destructor
//@{
    CWindow();
    virtual ~CWindow();
//@}

    ///close the window, calling CanClose
    bool Close(bool pForce=false);
    
    void SetWindowType(EWindowType pClass);
    EWindowType WindowType()   {   return mWindowType;    }

    void SetWindowActions(EWindowActions pActions);
    EWindowActions WindowActions()   {   return mWindowActions;    }

public:
///\name redrawing the window
//@{
    ///gets the paint mode
    EPaintMode PaintMode()               {   return mPaintMode;    }
    ///sets the paint mode
    void SetPaintMode(EPaintMode pMode);

    void SetOpacity(double);

    ///refresh a region in the window
    void Refresh(const CRegion&,bool pImmediate=false);
    ///refresh a region in the window
    void Refresh(const CRect_d &pRect,bool pImmediate=false)    {   Refresh(CRegion(pRect),pImmediate);         }
    ///refresh the whole contents
    void Refresh(bool pImmediate)                               {   Refresh(CRegion(nUnbounded),pImmediate);    }
    ///refresh the whole contents
    void Refresh()                                              {   Refresh(CRegion(nUnbounded),false);         }

    void Relayout(bool pExternal=false);
    
    ///start painting the window
    img::CGC *StartPainting(bool pClear=false);
    ///start painting a region in the window
    img::CGC *StartPainting(const CRegion&,bool pClear=false);
    ///end painting
    void EndPainting(img::CGC*);
    ///end painting a region
    void EndPainting(img::CGC*,const CRegion&);
//@}

///\name changing and querying visibility of the window
//@{
    ///shows (or hides) the window
    void Show(bool=true);
    ///hides the window
    void Hide()         {   Show(false);    }

    void Freeze();
    void Defreeze();
    
    ///return true if the window is visible
    bool Visible()    {   return GetFlag(EFlags::MAPPED)&&!(mPaintMode&EPaintMode::INVISIBLE);       }
    ///return true if the window is clickable
    bool Clickable();
    ///return true if the window is mapped
    bool Mapped() {   return GetFlag(EFlags::MAPPED);    }
    ///return true if the window is shown
    bool Mappable() {   return GetAttrib(EAttribs::MAPPABLE);    }
//@}

///\name window relationships
//@{
public:
    ///add a child to this window
    void Place(const PWindow &pChild);
    void Attach(const PWindow &pChild,EAttachPlace pPlace,EAttachFlags pFlags=ATTACH_NONE);
    void Attach(const PWindow &pH,const PWindow &pV,EAttachPlace pPlace=ATTACH_EAST|ATTACH_SOUTH);
    void Attach(const PWindow &pChild,EAttachRelative pPlace,const PWindow &pOther,EAttachFlags pFlags=ATTACH_NONE);

protected:
    void Manage(const PWindow &pChild);

public:
    ///remove a child from this window
    void RemoveChild(const PWindow &pChild);
    ///get the parent of the window
    CWindow *Parent()                   {   return mParent;         }
    ///get the frame for this window
    CWindow *GetTopLevel();
    ///get the ancestor window which has a physical window
    CWindow *GetPhysicalAncestor();
    
    CPhysicalWindow *Physical()         {   return mPhysical;       }
    
    bool IsTopLevel()                   {   return !mParent;        }
    bool IsMainWindow()                 {   return GetFlag(EFlags::MAINWINDOW); }
    bool IsModal()                      {   return GetFlag(EFlags::MODAL);      }
    
    //! true if this window is a descendant of \p pW
    bool IsDescendantOf(CWindow &pW);
    //! true if this window is the parent of \p pW
    bool IsParentOf(CWindow &pW);
    //! true if this window is an ancestor of \p pW
    bool IsAncestorOf(CWindow &pW);
    //! true if this windows is a sibling of \p pW (both have the same parent)
    bool IsSiblingOf(CWindow &pW);

    //! find the descendant at \p pPoint
    CWindow *FindMouseTargetAt(const CPoint_d &pPoint);
//@}

//! \name size & position
//@{
    void Map(const CPoint_d &pPos=nUndefined,const CSize_d &pSize=nUndefined,bool pClientCoordinates=true);
    void Map(const CRect_d &pPos,bool pClientCoordinates=true)  {   Map(pPos.TopLeft(),pPos.Size(),pClientCoordinates); }
    void Map(const CSize_d &pSize,bool pClientCoordinates=true) {   Map(nUndefined,pSize,pClientCoordinates);           }
    void Unmap();
    ///move the window
    void Move(const CPoint_d &pPos);
    ///resize the window
    void Resize(const CSize_d &pSize);
    ///move and resize the window
    void MoveResize(const CPoint_d &pPos,const CSize_d &pSize);
    ///move and resize the window
    void MoveResize(const CRect_d &pR)   {   MoveResize(pR.TopLeft(),pR.Size());  }

    ///put the window on top of all its siblings
    void BringToTop();
    ///put the window on bottom of all its siblings
    void SendToBottom();

    ///get the attach manager for this windowing
    CAttachManager *AttachManager(bool pCreate=true);
//@}

//! \name query info
//@{
    CPoint_d GetMousePos();

    CPoint_d Position();
    CSize_d WholeSize()                 {   return mWholeRect.Size();                           }
    CSize_d Size()                      {   return mClientRect.Size();                          }
    const CRect_d &ClientRect()         {   return mClientRect;                                 }
    const CRect_d &WholeRect()          {   return mWholeRect;                                  }

    CPoint_d GetPosInTopLevel();
    CPoint_d GetRootPos();
    const CRegion &GetWholeRegion();
    const CRegion &GetClientRegion();
    
    const img::CColor &BackColor()    {   return mBackColor;  }

    EEvents Events()                  {   return mEvents;     }
//@}

//! \name coordinate transformation
//@{
    ///transform coordinates from another window to this window (or to the screen)
    CPoint_d TranslateCoords(const CPoint_d &pPosInOther,const PWindow &pOther=nullptr);

    //! transform coordinates from device to client
    CPoint_d DeviceToClient(const CPoint_d&);
    //! transform coordinates from client to device
    CPoint_d ClientToDevice(const CPoint_d&);

    void ResetClientMatrix();
    void SetClientMatrix(const mat::C2DTransform &pMatrix);
    void MultClientMatrix(const mat::C2DTransform &pMatrix);
    void ClientMatrixScale(const CSize_d &pSize);
    void ClientMatrixScale(double pS)                   {   ClientMatrixScale(CSize_d(pS,pS));    }
    void ClientMatrixTranslate(const CPoint_d &pT);
    void ClientMatrixRotate(double pA);
    
    const mat::C2DTransform &ClientMatrix()
    {
        if(!mClientMatrix) mClientMatrix.reset(new mat::C2DTransform);
        return *mClientMatrix;
    }
    
//@}

    void SetMinSize(const CSize_d &pMinSize);
    const CSize_d &MinSize()    {   return mSizeMin;                }
    void SetMaxSize(const CSize_d &pMaxSize);
    const CSize_d &MaxSize()    {   return mSizeMax;                }
    void SetFixedSize(const CSize_d &pFixedSize);
    void SetPreferredSize(const CSize_d &pPreferredSize);
    const CSize_d &PreferredSize()    {   return mSizePreferred;                }
    void SetSizeStep(const CSize_d &pSizeStep);
    const CSize_d &SizeStep()    {   return mSizeStep;                }
    

//! \name focus
//@{
    ///gets the focus mode
    EFocusMode FocusMode()               {   return mFocusMode;    }
    ///sets the focus mode
    void SetFocusMode(EFocusMode pMode);
    ///unsets certain bits from the focus mode
    void UnsetFocusMode(EFocusMode pMode)   {   SetFocusMode(FocusMode()&~pMode);            }
    
    //! activate the top-level window
    void Activate();
    //! true if the window is active
    bool IsActive();
    ///set the focus to this window, an optionally activate the top-level window
    void SetFocus(bool pActivate=true);
    ///set the focus within this window to a certain descendant
    void SetFocusToChild(bool pActivate=true);
    ///remove focus from this window
    void DropFocus();
    ///circulate the focus
    bool CirculateFocus(ECirculate,const PWindow &pStop,bool pFirst=false);
    ///true if the window has the focus
    bool HasFocus();
//@}

//! \name input mode
//@{
    //! gets the input mode
    EInputMode InputMode()               {   return mInputMode;    }
    //! sets the input mode
    void SetInputMode(EInputMode pMode);
//@}

//! \name appearance
//@{
    //! sets the background to a color
    void SetBackColor(const img::CColor&);
    //! sets a transparent background
    void SetBackTransparent();
    //! sets no background
    void SetBackNone();

    //! sets the cursor
    void SetPointer(PPointer);
    //! sets the cursor to one of the predefined pointers
    void SetPointer(CPointer::EStock);

    PPointer Pointer()  {   return mPointer;    }

    //! sets the title of the window
    void SetTitle(const std::wstring& =std::wstring());
    //! returns the title of the window
    const std::wstring& Title()                  {   return mTitle;      }

    //! sets the icon to use for the window
    void SetIcon(PIcon);
///@}

//! \name finding windows
//@{
    ///finds the child at a certain position, and returns the position
    CWindow* FindChildAtAndPos(CPoint_d&,const PWindow &pSkip=nullptr);
    ///finds the child at a certain position
    CWindow* FindChildAt(const CPoint_d&,const PWindow &pSkip=nullptr);
    ///finds the child at a certain root position
    CWindow* FindChildAtRoot(const CPoint_d&,const PWindow &pSkip=nullptr);

    ///returns a vector with pointers to all children
    const std::vector<PWindow> &Children()    {   return mChildren;   }
    
    std::vector<PWindow> ManagedChildren();
//@}

protected:
//! \name events
//@{
    //! sets a bit in the event mask
    void SetEvent(EEvents pEvent)        {   SetEvents(mEvents|pEvent);      }
    //! unsets a bit in the event mask
    void UnsetEvent(EEvents pEvent)      {   SetEvents(mEvents&(~pEvent));   }

private:
//! \name event handlers
//@{
    //! OnExpose is only for drawing. This is for shaping before Exposing
    virtual void OnLayout() {}
    
    virtual void OnPreExpose() {}
    virtual void OnExpose(const CExposeEvent &pE) {}

    virtual void OnChar(const CKeyEvent &pE) {}
    virtual void OnKeyDown(const CKeyEvent &pE) {}
    virtual void OnKeyUp(const CKeyEvent &pE) {}

    virtual void OnButtonDown(const CMouseEvent &pE) {}
    virtual void OnButtonUp(const CMouseEvent &pE) {}
    virtual void OnMouseMove(const CMouseEvent &pE) {}
    virtual void OnMouseIn(const CMouseEvent &pE) {}
    virtual void OnMouseOut(const CMouseEvent &pE) {}
    virtual void OnMouseWheel(const CMouseEvent &pE) {}

    virtual void OnMove(const CGeometryEvent &pE) {}
    virtual void OnSize(const CGeometryEvent &pE) {}

    virtual void OnFocusIn(const CFocusEvent &pE) {}
    virtual void OnFocusOut(const CFocusEvent &pE) {}

    virtual void OnFirstShow() {}
    virtual void OnShow() {}
    virtual void OnHide() {}

    virtual void OnChild(CWindow &pChild,int /*pEvent*/) {}
//@}

public:
///\name hooks
//@{
    void AddCommitHook(const std::function<void(const CRegion&)> &pHook);
//@}

private:
//! \name internal creation functions
//@{
    void Invalidate(EValid pWhat)       {   mValid&=~pWhat;                 }
    void Validate(EValid pWhat)         {   mValid|=pWhat;                  }
    bool Valid(EValid pWhat)            {   return (mValid&pWhat)==pWhat;   }
//@}

//! \name internal geometry functions
//@{
    ///called when some of the regions are needed
    void RecalculateRegions();

    //! map status should be recalculated
    void RecalcMapStatus();
    //! called when the visibility should be recalculated
    void RecalcVisibility(bool pFromParent=false);

    void CalcMinSize();
    void CalcMaxSize();
//@}

///\name internal focus functions
//@{
    CWindow *FindFocusedDescendant();
//@}

public:
///\name events from child managers
//@{
    void OnChildManaged(CChildManager *pManager,bool pManaged);
//@}

private:
//! \name internal event management
//@{
    void DoLayout();
    void DoRefresh(const CRegion &pRegion);
    void ProcessMap(const CRect_d &pWholeRect);
    void ProcessUnmap();
    void ProcessExpose(CExposeHelper *pHelper,const CRegion &pDirtyRegion,CRegion &pCommitRegion);
    void SendSizeEvent();
    void SendChildEvent(CWindow &pChild,int pEvent);
    void SetEvents(EEvents);
//@}

///! \name internal flags
//@{
    //! sets the window flags
    void SetFlags(EFlags pFlags)    {   mFlags=pFlags;  }

    //! gets the window flags
    EFlags GetFlags()               {   return mFlags;  }

    //! sets a window flag
    void SetFlag(EFlags pFlag)      {   mFlags|=pFlag;  }
    //! unsets a window flag
    void UnsetFlag(EFlags pFlag)    {   mFlags&=(~pFlag);   }
    //! checks if a window flag is set
    bool GetFlag(EFlags pFlag)      {   return ((mFlags&pFlag)==pFlag); }

    bool GetAttrib(EAttribs pAttrib)
    {
        return (mAttribs&pAttrib)==pAttrib;
    }
    
    void SetAttrib(EAttribs pAttrib)
    {
        mAttribs|=pAttrib;
    }
    
    void UnsetAttrib(EAttribs pAttrib)
    {
        mAttribs&=~pAttrib;
    }
//@}

private:
/////////////////////
// related windows
//
    CPhysicalWindow *mPhysical;
    CWindow *mParent;
    std::vector<PWindow> mChildren;    ///<all children from top to bottom
    CCalendarTime mFocusTimestamp;
    
//////////////////////////////////////
// window attributes and event mask
//
    EFlags mFlags;
    EPaintMode mPaintMode;
    EFocusMode mFocusMode;
    EInputMode mInputMode;
    EAttribs mAttribs;
    EWindowType mWindowType;
    EWindowActions mWindowActions;
    EEvents mEvents;            //!<events the window wants to be notified about
    img::CColor mBackColor;
    PPointer mPointer;

    std::wstring mTitle;
    PIcon mIcon;

//////////////
// geometry
//
    ///window geometry
    CRect_d mWholeRect;         ///<the position and size of the window (in parent window coordinates)
    CRect_d mClientRect;        ///<the client rectangle (in window coordinates)

    CPoint_d mPosPreferred;
    CSize_d mSizeMin;
    CSize_d mSizeMax;
    CSize_d mSizePreferred;
    CSize_d mSizeStep;
    
    std::unique_ptr<mat::C2DTransform> mClientMatrix;

    EValid mValid;
    CRegion mRegionWhole;
    CRegion mRegionClient;
    CRegion mRegionDirty;
    
    std::unique_ptr<CAttachManager> mAttachManager;
    std::unique_ptr<SHooks> mHooks;

    friend class CWindowManager;
    friend class CAttachManager;
    friend class CAccelerator;
};

inline io::CTextWriter &operator<<(io::CTextWriter &pWriter,const PWindow &pWindow)
{
    pWriter << "pointer=" << (void*)pWindow.get() << " class=" << typeid(*pWindow).name();
    return pWriter;
}

///@}

/*namespace gui*/ } /*namespace buola*/ }

#endif
